#include <DB/Columns/ColumnString.h>
#include <DB/Interpreters/SetVariants.h>

namespace DB
{

namespace ErrorCodes
{
	extern const int UNKNOWN_SET_DATA_VARIANT;
	extern const int LOGICAL_ERROR;
}

void SetVariants::init(Type type_)
{
	type = type_;

	switch (type)
	{
		case Type::EMPTY: break;

	#define M(NAME) \
		case Type::NAME: NAME = std::make_unique<decltype(NAME)::element_type>(); break;
		APPLY_FOR_SET_VARIANTS(M)
	#undef M

		default:
			throw Exception("Unknown Set variant.", ErrorCodes::UNKNOWN_SET_DATA_VARIANT);
	}
}

size_t SetVariants::getTotalRowCount() const
{
	switch (type)
	{
		case Type::EMPTY: return 0;

	#define M(NAME) \
		case Type::NAME: return NAME->data.size();
		APPLY_FOR_SET_VARIANTS(M)
	#undef M

		default:
			throw Exception("Unknown Set variant.", ErrorCodes::UNKNOWN_SET_DATA_VARIANT);
	}
}

size_t SetVariants::getTotalByteCount() const
{
	switch (type)
	{
		case Type::EMPTY: return 0;

	#define M(NAME) \
		case Type::NAME: return NAME->data.getBufferSizeInBytes();
		APPLY_FOR_SET_VARIANTS(M)
	#undef M

		default:
			throw Exception("Unknown Set variant.", ErrorCodes::UNKNOWN_SET_DATA_VARIANT);
	}
}

SetVariants::Type SetVariants::chooseMethod(const ConstColumnPlainPtrs & key_columns, Sizes & key_sizes)
{
	/// Check if at least one of the specified keys is nullable.
	/// Create a set of nested key columns from the corresponding key columns.
	/// Here "nested" means that, if a key column is nullable, we take its nested
	/// column; otherwise we take the key column as is.
	ConstColumnPlainPtrs nested_key_columns;
	nested_key_columns.reserve(key_columns.size());
	bool has_nullable_key = false;

	for (const auto & col : key_columns)
	{
		if (col->isNullable())
		{
			const ColumnNullable & nullable_col = static_cast<const ColumnNullable &>(*col);
			nested_key_columns.push_back(nullable_col.getNestedColumn().get());
			has_nullable_key = true;
		}
		else
			nested_key_columns.push_back(col);
	}

	size_t keys_size = nested_key_columns.size();

	bool all_fixed = true;
	size_t keys_bytes = 0;
	key_sizes.resize(keys_size);
	for (size_t j = 0; j < keys_size; ++j)
	{
		if (!nested_key_columns[j]->isFixed())
		{
			all_fixed = false;
			break;
		}
		key_sizes[j] = nested_key_columns[j]->sizeOfField();
		keys_bytes += key_sizes[j];
	}

	if (has_nullable_key)
	{
		/// At least one key is nullable. Therefore we choose a method
		/// that takes into account this fact.
		if ((keys_size == 1) && (nested_key_columns[0]->isNumeric()))
		{
			/// We have exactly one key and it is nullable. We shall add it a tag
			/// which specifies whether its value is null or not.
			size_t size_of_field = nested_key_columns[0]->sizeOfField();
			if ((size_of_field == 1) || (size_of_field == 2) || (size_of_field == 4) || (size_of_field == 8))
				return SetVariants::Type::nullable_keys128;
			else
				throw Exception{"Logical error: numeric column has sizeOfField not in 1, 2, 4, 8.",
					ErrorCodes::LOGICAL_ERROR};
		}

		if (all_fixed)
		{
			/// Pack if possible all the keys along with information about which key values are nulls
			/// into a fixed 16- or 32-byte blob.
			if (keys_bytes > (std::numeric_limits<size_t>::max() - std::tuple_size<KeysNullMap<UInt128>>::value))
				throw Exception{"Aggregator: keys sizes overflow", ErrorCodes::LOGICAL_ERROR};
			if ((std::tuple_size<KeysNullMap<UInt128>>::value + keys_bytes) <= 16)
				return SetVariants::Type::nullable_keys128;
			if ((std::tuple_size<KeysNullMap<UInt256>>::value + keys_bytes) <= 32)
				return SetVariants::Type::nullable_keys256;
		}

		/// Fallback case.
		return SetVariants::Type::hashed;
	}

	/// Если есть один числовой ключ, который помещается в 64 бита
	if (keys_size == 1 && nested_key_columns[0]->isNumericNotNullable())
	{
		size_t size_of_field = nested_key_columns[0]->sizeOfField();
		if (size_of_field == 1)
			return SetVariants::Type::key8;
		if (size_of_field == 2)
			return SetVariants::Type::key16;
		if (size_of_field == 4)
			return SetVariants::Type::key32;
		if (size_of_field == 8)
			return SetVariants::Type::key64;
		throw Exception("Logical error: numeric column has sizeOfField not in 1, 2, 4, 8.", ErrorCodes::LOGICAL_ERROR);
	}

	/// Если ключи помещаются в N бит, будем использовать хэш-таблицу по упакованным в N-бит ключам
	if (all_fixed && keys_bytes <= 16)
		return SetVariants::Type::keys128;
	if (all_fixed && keys_bytes <= 32)
		return SetVariants::Type::keys256;

	/// If there is single string key, use hash table of it's values.
	if (keys_size == 1 && (typeid_cast<const ColumnString *>(nested_key_columns[0]) || typeid_cast<const ColumnConstString *>(nested_key_columns[0])))
		return SetVariants::Type::key_string;

	if (keys_size == 1 && typeid_cast<const ColumnFixedString *>(nested_key_columns[0]))
		return SetVariants::Type::key_fixed_string;

	/// Otherwise, will use set of cryptographic hashes of unambiguously serialized values.
	return SetVariants::Type::hashed;
}

}
